package ro.flashbite.helpers
{
	import ro.flashbite.errors.ArgumentIncompatibleError;
	import ro.flashbite.errors.DontInstanciateError;

	/**
	 * Static helpers for working with Array objects
	 * 
	 * v 1.0
	 * 
	 * @author Adrian Barbu
	 */	
	public final class ArrayHelpers
	{
		public static const		DIRECTION_LEFT	:	uint = 0;
		public static const		DIRECTION_RIGHT	:	uint = 1;
		
		public function ArrayHelpers() { throw new DontInstanciateError(); }
		
		/**
		 * Create a clone of one array 
		 * 
		 * @param arr : (Array) collection of items to be cloned
		 * @return : (Array) a clone of the initial array
		 */		
		public static function clone(arr:Array):Array
		{
			if (arr == null) {
				throw new ArgumentIncompatibleError("arr");
			}
			
			return arr.concat();		 	
		}
		
		/**
		 * Shuffles an array so that its items are in random order
		 * 
		 * @param arr : (Array) collection of items to be shuffled
		 */		
		public static function shuffle(arr:Array):void 
		{
			if (arr == null){
				throw new ArgumentIncompatibleError("arr");
			}
			
			var i			:int,
				swap		:int,
				temp		:*,
				len     	:int = arr.length,
				randomInt	:Function = MathHelpers.randomInt;
			
			if (len > 1) {
				for (i = 0; i < len; i++) {
					swap = randomInt(0, len - 1);
					temp = arr[swap];
					arr[swap] = arr[i];
					arr[i] = temp;
				}
			}
		}
		
		/**
		 * Gets a shuffled array with specific dimension from a starting array
		 *  
		 * @param arr : (Array) array to use
		 * @param num : (uint) length of the returning shuffled array
		 * @return : (Array) a shuffled array with specific dimension
		 */		
		public static function getSpecificRandomChoices(arr:Array, num:uint):Array
		{
			if (arr == null) {
				throw new ArgumentIncompatibleError("arr");
			}
			if (num == 0 || num > arr.length) {
				throw new ArgumentIncompatibleError("nmb");
			}
			
			//create a clone, make a shuffle, remove last elements so that the returning array will have num items
			var randomArray:Array = clone(arr);
			shuffle(randomArray);
			randomArray = randomArray.slice(0, num);
			
			return randomArray;
		}
		
		/**
		 * Create an array, from given one, of a specific dimension, starting from some position and circling throught given array
		 * example1: arr = ["a", "b", "c"]; mutantArray(arr, 5) = ["a", "b", "c", "a", "b"];
		 * example2: arr = ["a", "b", "c", "d", "e"]; subArray(4, 3, arr, "right") = ["e", "a", "b"]; subArray(4, 3, arr, "left") = ["e", "d", "c"];
		 *  
		 * @param arr : (Array) starting array
		 * @param length : (uint) length of the new array
		 * @param start : (uint) position in array to start
		 * @param direction : (uint) left or right, the directio to 'travel' in array
		 * @return : (Array) a newly created mutant array, bigger or smaller than the parent array
		 */		
		public static function mutantArray(arr:Array, length:uint, start:uint = 0, direction:uint = ArrayHelpers.DIRECTION_RIGHT):Array
		{
			if (arr == null) {
				throw new ArgumentIncompatibleError("arr");
			}
			if (length == 0) {
				throw new ArgumentIncompatibleError("length");
			}
			if (start >= arr.length) {
				throw new ArgumentIncompatibleError("start");
			}
			if (direction != DIRECTION_LEFT && direction != DIRECTION_RIGHT) {
				throw new ArgumentIncompatibleError("direction");
			}
			
			var newArr :Array = [],
				i		:int = start,
				arrLen  :int = arr.length;
			
			while (newArr.length < length) {
				newArr.push(arr[i]);
				if (direction == DIRECTION_RIGHT) {
					i++;
					if (i >= arrLen) i = 0;
				} else if (direction == DIRECTION_LEFT) {
					i--;
					if (i < 0) i = arrLen -1;
				}
			}
			
			return newArr;
		}
		
		/**
		 * Removes a value from array
		 * 
		 * @param arr : (Array) array to search the value
		 * @param value : (*[Object]) value to search in array
		 * @return : (Boolean) true if found and removed from array, false otherwise
		 */		
		public static function removeValue(arr:Array, value:*):Boolean
		{
			if (arr == null) {
				throw new ArgumentIncompatibleError("arr");
			}
			if (value == null) {
				throw new ArgumentIncompatibleError("value");
			}
			
			var removed:Boolean = false;
			
			while (arr.indexOf(value) > -1) {
				arr.splice(arr.indexOf(value), 1);
				removed = true;
			}
			
			return removed;    
		}
		
		/**
		 * Removes all duplicates from array
		 *  
		 * @param arr : (Array) array to use
		 * @param exception : (*[Object]) value to be excepted when searching for duplicates
		 * @return : (Array) a clone of initial array with duplicates removed
		 */		
		public static function removeDuplicates(arr:Array, exception:* = null):Array
		{
			if (arr == null) {
				throw new ArgumentIncompatibleError("arr");
			}
			
			var clone	: Array = clone(arr),
				value	: *,
				i		: int,
				j		: int;
			
				for (j = clone.length - 1; j > -1; j--) {
					value = clone[j];
					for (i = j - 1; i > -1; i--) {				
						if (clone[i] === value && value != exception) {
							clone.splice(i, 1);
						}
					}
				}
			
			return clone;
		}
		
		/**
		 * Adds all elements from array
		 * 
		 * @param arr : (Array) array used to make sum
		 * @return : (Number) sum off all numbers in array
		 */		
		public static function sum(arr:Array):Number 
		{
			if (arr == null) {
				throw new ArgumentIncompatibleError("arr");
			}
			
			var total	:int = 0,
				i		:uint,
				len		:uint = arr.length;
			
			for (i = 0; i < len; i++) {
				total += arr[i];
			}
			
			return total;
		}
		
		/**
		 * Return max element from array with numbers
		 * 
		 * @param arr : (Array) array to use
		 * @return : (Number) max element in array
		 */		
		public static function max(arr:Array):Number 
		{
			if (arr == null) {
				throw new ArgumentIncompatibleError("arr");
			}
			
			var max	:Number = arr[0],
				len	:int = arr.length,
				i	:int,
				maxFunc:Function = Math.max;
			
			for (i = 1; i < len; i++) 
				max = maxFunc(max, arr[i]);
			
			return max;	
		}
		
		/**
		 * Return min element from array with numbers
		 * 
		 * @param arr : (Array) array to use
		 * @return : (Number) min element in array
		 */	
		public static function min(arr:Array):Number 
		{
			if (arr == null) {
				throw new ArgumentIncompatibleError("arr");
			}
			
			var min	:Number = arr[0],
				len	:int = arr.length,
				i	:int,
				minFunc:Function = Math.min;
			
			for (i = 1; i < len; i++) 
				min = minFunc(min, arr[i]);
			
			return min;	
		}
	}
}